﻿using System;
using System.Collections.Generic;
using QTPlugin;
using QTPlugin.Interop;
using System.Windows.Forms;
using System.IO;
using System.Drawing;
using Microsoft.Win32;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Runtime.InteropServices.ComTypes;
using System.ComponentModel;
using System.Text.RegularExpressions;
using Kawanon.Usability;
using System.Runtime.Serialization;
using System.IO.Compression;
using System.Runtime.Serialization.Formatters.Binary;
using Kawanon.Usability.Visualization;
using System.Globalization;

namespace Kawanon.QtHoney
{
	public static class Persistence
	{
		private const int ARCHIVE_VERSION_MISCAST = 1;

		public static void ArchiveFromArray(Stream stream, object[] list)
		{
			DeflateStream deflate = new DeflateStream(stream, CompressionMode.Compress, true);

			BinaryFormatter bin = new BinaryFormatter();
			Array.ForEach(list, obj => bin.Serialize(deflate, obj));

			deflate.Flush();	// フラッシュして閉じないと読み込みで失敗する
			deflate.Close();	//（エンドマークが書き込まれない）
		}

		public static void SaveBinary(string val, object[] list)
		{
			MemoryStream ms = new MemoryStream();
			ArchiveFromArray(ms, list);

			RegistryKey key = Plugins.OpenRegistryKey(true);
			key.SetValue(val, ms.ToArray(), RegistryValueKind.Binary);
			key.Flush();
			key.Close();
		}

		public static Stream CreateValueStream(string name)
		{
			RegistryKey key = Plugins.OpenRegistryKey(false);
			if (key == null)
			{
				return null;
			}

			byte[] bytes = (byte[])key.GetValue(name);
			if (bytes == null)
			{
				return null;
			}
			MemoryStream ms = new MemoryStream(bytes);
			return ms;
		}

		public delegate object Loader();

		public static Loader CreateLoader(Stream istream)
		{
			DeflateStream inflate = new DeflateStream(istream, CompressionMode.Decompress);
			BinaryFormatter bin = new BinaryFormatter();
			bin.Binder = new LocalSerializationBinder();

			return delegate()
			{
				return bin.Deserialize(inflate);
			};
		}

		private static string GetArchiveName(Type t)
		{
			return t.Name.Replace("Information", "");
		}

		public static T GetInformation<T>()
			where T: class
		{
			object info = LoadInformation<T>();
			if (info == null)
			{
				return (T)Activator.CreateInstance(typeof(T));
			}
			return (T)info;
		}

		public static T LoadInformation<T>()
			where T: class
		{
			Stream stream = CreateValueStream(GetArchiveName(typeof(T)));
			if (stream == null)
			{
				return null;
			}
			return (T)LoadInformation(stream);
		}

		public static object LoadInformation(Stream istream)
		{
			Loader loader = CreateLoader(istream);

			int ver = (int)loader();
			if (ver != ARCHIVE_VERSION_MISCAST)
			{
				throw new ArchiveVersionMismatchException();
			}

			return loader();
		}

		public static void SaveInformation(object info)
		{
			Type t = info.GetType();
			string name = GetArchiveName(t);

			SaveBinary(name, new object[] { ARCHIVE_VERSION_MISCAST, info });

			//if (t.Equals(typeof(RenderingInformation)))
			//{
			Plugins.NotifySettingsChanged();
			//}
		}
	}

	//Thanks: http://hongliang.seesaa.net/article/53459099.html
	[AttributeUsage(AttributeTargets.Field)]
	public class EnumDisplayNameAttribute : Attribute
	{
		public EnumDisplayNameAttribute(string name)
		{
			this.name = name;
		}

		public string Name
		{
			get { return GetName(CultureInfo.CurrentCulture); }
		}

		public virtual string GetName(CultureInfo culture)
		{
			return this.name;
		}
		private string name;
	}

	//Thanks: http://hongliang.seesaa.net/article/53459099.html
	public class EnumDisplayNameConverter : EnumConverter
	{
		public EnumDisplayNameConverter(Type type)
			: base(type)
		{
		}

		public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object valueToConvert)
		{
			string value = valueToConvert as string;
			if (value != null)
			{
				foreach (FieldInfo field in base.EnumType.GetFields())
				{
					string name = this.GetDisplayName(field, culture);
					if (name == value)
					{
						return field.GetValue(null);
					}
				}
			}
			return base.ConvertFrom(context, culture, valueToConvert);
		}

		public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
		{
			if (value == null)	// 複数項目を選択したとき
			{
				return null;
			}
			if (destinationType == typeof(string))
			{
				string valueName = Enum.GetName(base.EnumType, value);
				if (valueName != null)
				{
					FieldInfo field = base.EnumType.GetField(valueName);
					string name = this.GetDisplayName(field, culture);
					if (name != null)
					{
						return name;
					}
				}
			}
			return base.ConvertTo(context, culture, value, destinationType);
		}

		private string GetDisplayName(FieldInfo field, CultureInfo culture)
		{
			if (field == null)
			{
				return null;
			}
			Type type = typeof(EnumDisplayNameAttribute);
			Attribute attr = Attribute.GetCustomAttribute(field, type);
			EnumDisplayNameAttribute disp = attr as EnumDisplayNameAttribute;

			return (disp == null) ? null : disp.GetName(culture);
		}
	}

	// QTTabBar でロード時に BinaryFormatter がアセンブリを特定できない問題の対策（GACがらみ？）
	public class LocalSerializationBinder : SerializationBinder
	{
		public override Type BindToType(string assemblyName, string typeName)
		{
			//NOTE: アセンブリから直接タイプ名を取得する
			typeName = typeName.Replace("DefinedCommand", "BuiltInCommand");

			Type ty = Assembly.GetExecutingAssembly().GetType(typeName);
			//DebugHelpers.Echo(ty +"_ "+ typeName +"_ "+ assemblyName);

			//StringBuilder buf = new StringBuilder();
			//foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies())
			//{
			//    buf.Append(asm.FullName);
			//    buf.Append("\r\n");
			//}
			//DebugHelpers.Echo(buf);
			return ty;
		}
	}

	public class PathExpander
	{
		private static Regex s_regCurrentFolder = new Regex("%c%", RegexOptions.Compiled | RegexOptions.IgnoreCase);
		private static Regex s_regFolders = new Regex("%d%", RegexOptions.Compiled | RegexOptions.IgnoreCase);
		private static Regex s_regSelectionOrCurrentFolder = new Regex("%cd%", RegexOptions.Compiled | RegexOptions.IgnoreCase);
		private static Regex s_regFiles = new Regex("%f%", RegexOptions.Compiled | RegexOptions.IgnoreCase);
		private static Regex s_regSelection = new Regex("%s%", RegexOptions.Compiled | RegexOptions.IgnoreCase);

		private string m_currentFolder;
		private string m_selectionOrCurrentFolder;
		private string m_strFiles = String.Empty, m_strFolders = String.Empty, m_strSelection = String.Empty;

		public PathExpander(Address currentFolder, Address[] selection)
		{
			m_currentFolder = Utility.Quote(Utility.AddressToPath(currentFolder));
			m_currentFolder = m_currentFolder != null ? m_currentFolder : String.Empty;

			GenerateStrings(selection);

			m_selectionOrCurrentFolder = (m_strFolders != String.Empty) ? m_strFolders : m_currentFolder;
		}

		private string Concat(string[] paths)
		{
			return String.Join(" ", Array.ConvertAll(paths, path => Utility.Quote(path)));
		}

		private void GenerateStrings(Address[] addrs)
		{
			if (addrs == null)
			{
				return;
			}

			string[] paths = Array.ConvertAll<Address, string>(addrs, Utility.AddressToPath);

			paths = Array.FindAll(paths, path => !String.IsNullOrEmpty(path));
			// 仮想フォルダも含める
			string[] folders = Array.FindAll(paths, path => !File.Exists(path));
			string[] files = Array.FindAll(paths, path => Array.IndexOf(folders, path) == -1);

			m_strSelection = Concat(paths);
			m_strFiles = Concat(files);
			m_strFolders = Concat(folders);
		}

		public string Replace(string str)
		{
			if (str == null)
			{
				return null;
			}
			str = s_regSelection.Replace(str, m_strSelection);
			str = s_regFiles.Replace(str, m_strFiles);
			str = s_regFolders.Replace(str, m_strFolders);
			str = s_regCurrentFolder.Replace(str, m_currentFolder);
			str = s_regSelectionOrCurrentFolder.Replace(str, m_selectionOrCurrentFolder);
			return str;
		}
	}

	public class YesNoConverter : StringConverter
	{
		public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
		{
			return destinationType == typeof(bool);
		}

		public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
		{
			return sourceType == typeof(string);
		}

		public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
		{
			if (destinationType == typeof(string) && value is bool)
			{
				return (bool)value ? "はい" : "いいえ";
			}
			return base.ConvertTo(context, culture, value, destinationType);
		}

		public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
		{
			if (value is string)
			{
				string str = (string)value;
				if (str == "はい" || str == "True")
				{
					return true;
				}
				else if (str == "いいえ" || str == "False")
				{
					return false;
				}
				throw new ArgumentException("unknown format: " + str);
			}
			return base.ConvertFrom(context, culture, value);
		}

		public override TypeConverter.StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
		{
			return new StandardValuesCollection(new bool[] { true, false });
		}

		public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
		{
			return true;
		}
	}

	public class GroupMenuFactory	// GroupEditor でも使う？ (Server == null でメニューを無効にして)
	{
		public IPluginServer Server { get; set; }
		public bool Expandable { get; set; }
		//public ImageList ImageList { get; set; }

		private ToolStripMenuItem CreateGroupItem(string name, string[] paths)
		{
			ToolStripMenuItem group = new ToolStripMenuItem(name);
			if (paths.Length > 0)
			{
				group.Image = Utility.GetIconBitmap(paths[0], false);
			}

			EventHandler handler = new EventHandler(delegate(object sender, EventArgs e)
			{
				if (Server != null)
				{
					Server.OpenGroup(new string[] { name });
				}
				//group.HideDropDown();	// これは効果が無い？
				group.DropDown = null;
				group.Owner.Hide();
			});

			if (Expandable)
			{
				//group.DoubleClickEnabled = true;
				//group.DoubleClick += handler;
				group.Click += handler;

				foreach (string path in paths)
				{
					ToolStripMenuItem child = new ToolStripMenuItem(
						Utility.IsSpecialFolder(path) ? Utility.GetDisplayName(path) : path);
					//BUG: ImageIndex を指定しても描画されない
					child.Image = Utility.GetIconBitmap(path, false);
					Address addr = new Address(path);	// path を使うと最後の要素になってしまう
					child.Click += new EventHandler(delegate(object sender, EventArgs e)
					{
						if (Server != null)
						{
							Server.CreateTab(addr, -1, false, true);
						}
					});
					group.DropDownItems.Add(child);
				}
			}
			else
			{
				group.Click += handler;
			}
			return group;
		}

		public void CreateItems(ToolStripItemCollection items)
		{
			RegistryKey key = Registry.CurrentUser.OpenSubKey(@"Software\Quizo\QTTabBar\Groups", false);
			foreach (string name in key.GetValueNames())
			{
				string val = key.GetValue(name) as string;
				if (val == null)
				{
					continue;
				}
				if (val == String.Empty)
				{
					items.Add(new ToolStripSeparator());
					continue;
				}

				string[] paths = val.Split(';');
				ToolStripMenuItem group = CreateGroupItem(name, paths);
				items.Add(group);
			}
			key.Close();
		}
	}

	public static class Plugins
	{
		// サーバはウィンドウごとに異なる
		//public static IPluginServer Server { get; set; }
		public static List<IPluginClient> Instances { get; private set; }

		static Plugins()
		{
			Instances = new List<IPluginClient>();
		}

		public static RegistryKey OpenRegistryKey(bool writable)
		{
			return OpenRegistryKey(typeof(Plugins), writable);
		}

		private static RegistryKey OpenRegistryKey(Type t, bool writable)
		{
			string ns = t.Namespace.Replace('.', '\\');
			string key = String.Format(@"Software\Quizo\QTTabBar\Plugins\Settings\{0}", ns);
			if (writable)
			{
				return Registry.CurrentUser.CreateSubKey(key);
			}
			else
			{
				return Registry.CurrentUser.OpenSubKey(key);
			}
		}

		public static RegistryKey OpenClassRegistryKey(Type t, bool writable)
		{
			RegistryKey parent = OpenRegistryKey(writable);
			if (writable)
			{
				return parent.CreateSubKey(t.Name);
			}
			else
			{
				if (parent == null)
				{
					return null;
				}
				return parent.OpenSubKey(t.Name);
			}
		}

		public static event EventHandler SettingsChanged;

		public static void NotifySettingsChanged()
		{
			Utility.Raise(SettingsChanged);
		}
	}

	public class PluginClient : IPluginClient
	{
		protected IPluginServer m_server = null;
		protected IShellBrowser m_browser = null;

		public virtual bool HasOption
		{
			get
			{
				return false;
			}
		}

		public void Open(IPluginServer pluginServer, IShellBrowser shellBrowser)
		{
			m_server = pluginServer;
			m_browser = shellBrowser;

			OnOpen();
		}

		public virtual void Close(EndCode endCode)
		{
		}

		public virtual bool QueryShortcutKeys(out string[] actions)
		{
			actions = new string[] { };
			return false;
		}

		public virtual void OnOption()
		{
		}

		public virtual void OnShortcutKeyPressed(int index)
		{
		}

		public virtual void OnMenuItemClick(MenuType menuType, string menuText, ITab tab)
		{
		}

		protected virtual void OnOpen()
		{
		}
	}

	public class BarButton : PluginClient, IBarButton
	{
		public virtual bool ShowTextLabel
		{
			get
			{
				return false;
			}
		}

		public virtual string Text
		{
			get
			{
				return String.Empty;
			}
		}

		public virtual void InitializeItem()
		{
			// ボタンバーにこのボタンが追加されるたびに呼ばれます。
		}

		public virtual Image GetImage(bool fLarge)
		{
			return null;
		}

		public virtual void OnButtonClick()
		{
		}

	}

	public class BarDropButton : BarButton, IBarDropButton
	{
		public virtual bool IsSplitButton
		{
			get
			{
				return false;
			}
		}

		public virtual void OnDropDownItemClick(ToolStripItem item, MouseButtons mouseButton)
		{
		}

		public virtual void OnDropDownOpening(ToolStripDropDownMenu menu)
		{
		}
	}

	//public class BarCustomItem : BarButton, IBarCustomItem
	//{
	//    public ToolStripItem CreateItem(bool fLarge, QTPlugin.DisplayStyle displayStyle)
	//    {
	//        return null;
	//    }
	//}


}
